package com.corpgovernment.organization.service.impl;

import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.ReUtil;
import com.corpgovernment.api.organization.model.redemptioncode.GetRedemptionCodeRequest;
import com.corpgovernment.api.organization.model.redemptioncode.GetRedemptionCodeResponse;
import com.corpgovernment.api.organization.model.redemptioncode.RedemptionCodeVo;
import com.corpgovernment.api.organization.vo.DownloadResponse;
import com.corpgovernment.common.base.BaseUserInfo;
import com.corpgovernment.common.common.CorpBusinessException;
import com.corpgovernment.costcenter.vo.RedemptionCodeExcelColumnVo;
import com.corpgovernment.job.batchimport.entity.FillErrorEntity;
import com.corpgovernment.job.batchimport.entity.db.BatchImportErrorLineDo;
import com.corpgovernment.job.batchimport.entity.db.BatchImportRecordDo;
import com.corpgovernment.job.batchimport.enums.BatchImportStatusEnum;
import com.corpgovernment.job.batchimport.enums.BatchImportTypeEnum;
import com.corpgovernment.job.batchimport.mapper.BatchImportErrorLineMapper;
import com.corpgovernment.job.batchimport.mapper.BatchImportRecordMapper;
import com.corpgovernment.job.batchimport.util.BatchImportExcelUtils;
import com.corpgovernment.job.controller.vo.job.PageBatchImportJobRecordReqVo;
import com.corpgovernment.job.controller.vo.job.PageBatchImportJobRecordRespVo;
import com.corpgovernment.job.enums.DeletedEnum;
import com.corpgovernment.organization.constant.OrganizationResponseCodeEnum;
import com.corpgovernment.organization.dataloader.BasicOssClientLoader;
import com.corpgovernment.organization.entity.db.MbOrgEmployeeInfo;
import com.corpgovernment.organization.entity.db.MbRedemptionCode;
import com.corpgovernment.organization.mapper.MbRedemptionCodeMapper;
import com.corpgovernment.organization.service.RedemptionCodeService;
import com.corpgovernment.organization.util.ExcelUtils;
import com.ctrip.corp.obt.generic.utils.CollectionUtils;
import com.ctrip.corp.obt.generic.utils.JsonUtils;
import com.ctrip.corp.obt.generic.utils.StringUtils;
import com.ctrip.corp.obt.shard.context.PageContext;
import com.ctrip.corp.obt.shard.sql.mybatis.interceptor.pagination.IPage;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.entity.ContentType;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.web.multipart.MultipartFile;
import tk.mybatis.mapper.entity.Example;
import tk.mybatis.mapper.entity.Example.Criteria;

import javax.annotation.Resource;

import static com.corpgovernment.organization.constant.OrganizationResponseCodeEnum.OSS_ERROR;

/**
 * @author xbqi
 * @date 2023/11/2
 */
@Slf4j
@Service
public class RedemptionCodeServiceImpl implements RedemptionCodeService {

    @Autowired
    private MbRedemptionCodeMapper mbRedemptionCodeMapper;
    @Autowired
    private ExcelUtils excelUtils;
    @Autowired
    private TransactionTemplate transactionTemplate;
    @Resource
    private BatchImportRecordMapper batchImportRecordMapper;
    @Resource
    private BatchImportErrorLineMapper batchImportErrorLineMapper;
    @Autowired
    private BatchImportExcelUtils batchImportExcelUtils;
    @Autowired
    private BasicOssClientLoader basicOssClientLoader;
    private static final String ERROR_FILE_PREFIX = "【错误原因导出】";

    @Override
    public GetRedemptionCodeResponse listRedemptionCode(GetRedemptionCodeRequest request) {
        GetRedemptionCodeResponse response = new GetRedemptionCodeResponse();
        response.setPageNum(request.getPageNum());
        response.setPageSize(request.getPageSize());

        Example example = new Example(MbRedemptionCode.class);
        Criteria criteria = example.createCriteria();
        if (StringUtils.isNotBlank(request.getCode())) {
            criteria.andLike("redemptionCode", "%" + request.getCode() + "%");
        }

        List<MbRedemptionCode> mbRedemptionCodeList = mbRedemptionCodeMapper.selectByExample(example);
        log.info("初次获取验证码数量：{}", mbRedemptionCodeList.size());
        // 根据名字过滤
        if (StringUtils.isNotBlank(request.getName())) {
            mbRedemptionCodeList = mbRedemptionCodeList.stream()
                    .filter(e -> StringUtils.contains(e.getName(), request.getName()))
                    .collect(Collectors.toList());
        }

        List<Long> ids = mbRedemptionCodeList.stream().map(MbRedemptionCode::getId).collect(Collectors.toList());
        if (!CollectionUtils.isEmpty(ids)) {
            example = new Example(MbRedemptionCode.class);
            example.createCriteria().andIn("id", ids);
        }
        log.info("过滤后验证码数量：{}", mbRedemptionCodeList.size());
        IPage<MbRedemptionCode> page = PageContext.startPage(request.getPageNum(), request.getPageSize());
        mbRedemptionCodeMapper.selectByExample(example);
        mbRedemptionCodeList = page.getRecords();

        response.setCount((int) page.getTotal());
        response.setTotalPage((int) page.getPages());

        if (!CollectionUtils.isEmpty(mbRedemptionCodeList)) {
            List<RedemptionCodeVo> redemptionCodeVoList = mbRedemptionCodeList.stream()
                    .map(this::convertToRedemptionCodeVo).collect(Collectors.toList());
            response.setList(redemptionCodeVoList);
        }
        // 查询未分配的兑换码数量
        Example exampleCount = new Example(MbRedemptionCode.class);
        exampleCount.createCriteria().andIsNull("uid");
        int unRedemptionCount = mbRedemptionCodeMapper.selectCountByExample(exampleCount);
        response.setUnRedemptionCount(unRedemptionCount);
        return response;
    }

    @Override
    public DownloadResponse syncRedemptionCode(MultipartFile file, BaseUserInfo baseUserInfo) {
        if (file == null) {
            throw new CorpBusinessException(OrganizationResponseCodeEnum.FILE_IS_NULL);
        }
        if (file.getOriginalFilename() == null || (!file.getOriginalFilename().endsWith(".xls")
                && !file.getOriginalFilename().endsWith(".xlsx"))) {
            throw new CorpBusinessException(OrganizationResponseCodeEnum.SPREADSHEET_FORMAT_ERROR);
        }

        try {
            Workbook workbook = null;
            if (file.getOriginalFilename().endsWith(".xls")) {
                workbook = new HSSFWorkbook(file.getInputStream());
            } else if (file.getOriginalFilename().endsWith(".xlsx")) {
                workbook = new XSSFWorkbook(file.getInputStream());
            }

            // 取出第一页
            Sheet sheet = workbook.getSheetAt(0);
            // Excel内容
            List<RedemptionCodeExcelColumnVo> beanListFromExcel =
                    excelUtils.getBeanListFromExcel(sheet, RedemptionCodeExcelColumnVo.class);
            log.info("文件转化兑换码：{}", JsonUtils.toJsonString(beanListFromExcel));
            if(CollectionUtils.isEmpty(beanListFromExcel)) {
                throw new CorpBusinessException(OrganizationResponseCodeEnum.FILE_IS_NULL);
            }

            int lineCount = beanListFromExcel.size();
            int errorCount = 0;
            // 保存批量导入记录
            BatchImportRecordDo batchImportRecordDo = new BatchImportRecordDo(BatchImportTypeEnum.REDEMPTION.name(),
                    file.getOriginalFilename(), baseUserInfo);
            // 错误记录
            List<BatchImportErrorLineDo> batchImportErrorLineDoList = new ArrayList<>(lineCount);
            List<FillErrorEntity> fillErrorEntityList = new ArrayList<>(0);
            if (CollectionUtils.isNotEmpty(beanListFromExcel)) {
                // 已存在的兑换码
                Set<String> codeSet = mbRedemptionCodeMapper.selectAll().stream()
                        .map(MbRedemptionCode::getRedemptionCode)
                        .collect(Collectors.toSet());
                List<MbOrgEmployeeInfo> mbOrgEmployeeInfos = mbRedemptionCodeMapper.listUidNotBindCode();
                int uidIndex = 0;
                for ( int i=0; i<beanListFromExcel.size(); i++) {
                    RedemptionCodeExcelColumnVo redemptionCodeExcelColumnVo = beanListFromExcel.get(i);
                    if(StringUtils.isBlank(redemptionCodeExcelColumnVo.getCode()) && StringUtils.isBlank(redemptionCodeExcelColumnVo.getCodeLink())) {
                        lineCount--;
                        continue;
                    }
                    if (StringUtils.isBlank(redemptionCodeExcelColumnVo.getCode())) {
                        errorCount++;
                        batchImportErrorLineDoList.add(new BatchImportErrorLineDo(
                                i+1,
                                OrganizationResponseCodeEnum.REDEMPTION_CODE_IS_NULL.message()));
                        fillErrorEntityList.add(new FillErrorEntity(i,
                                1, OrganizationResponseCodeEnum.REDEMPTION_CODE_IS_NULL.message()));
                        continue;
                    }
                    if(StringUtils.isBlank(redemptionCodeExcelColumnVo.getCodeLink())) {
                        errorCount++;
                        batchImportErrorLineDoList.add(new BatchImportErrorLineDo(
                                i+1,
                                OrganizationResponseCodeEnum.REDEMPTION_CODE_URL_IS_NULL.message()));
                        fillErrorEntityList.add(new FillErrorEntity(i+1,
                                2, OrganizationResponseCodeEnum.REDEMPTION_CODE_URL_IS_NULL.message()));
                        continue;
                    }
                    if (codeSet.contains(redemptionCodeExcelColumnVo.getCode())) {
                        batchImportErrorLineDoList.add(new BatchImportErrorLineDo(
                                i+1,
                                OrganizationResponseCodeEnum.REDEMPTION_CODE_EXISTS.message()));
                        fillErrorEntityList.add(new FillErrorEntity(i+1,
                                1, OrganizationResponseCodeEnum.REDEMPTION_CODE_EXISTS.message()));
                        errorCount++;
                        continue;
                    }

                    MbRedemptionCode mbRedemptionCode = new MbRedemptionCode();
                    mbRedemptionCode.setRedemptionCode(redemptionCodeExcelColumnVo.getCode());
                    mbRedemptionCode.setRedemptionCodeLink(redemptionCodeExcelColumnVo.getCodeLink());
                    // 如果员工数量不够就不绑定
                    if (uidIndex < mbOrgEmployeeInfos.size()) {
                        mbRedemptionCode.setUid(mbOrgEmployeeInfos.get(uidIndex).getUid());
                        mbRedemptionCode.setName(mbOrgEmployeeInfos.get(uidIndex).getName());
                        uidIndex++;
                    }
                    mbRedemptionCodeMapper.insert(mbRedemptionCode);
                }
            }
            // 保存日志数据
            batchImportRecordDo.setLineCount(lineCount);
            batchImportRecordDo.setErrorCount(errorCount);
            batchImportRecordDo.setSuccessCount(lineCount-errorCount);
            if(errorCount > 0) {
                batchImportRecordDo.setStatus(BatchImportStatusEnum.FAIL.name());
                // 输出错误文件
                // 拷贝流
                InputStream inputStream = file.getInputStream();
                byte[] bytes = IoUtil.readBytes(inputStream);
                XSSFWorkbook workbookError = new XSSFWorkbook(IoUtil.toStream(bytes));
                batchImportExcelUtils.fillError(workbookError, fillErrorEntityList);
                ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
                workbook.write(byteArrayOutputStream);
                String errorFileUrl = basicOssClientLoader.putPrivateObject(new MockMultipartFile(
                        ERROR_FILE_PREFIX + batchImportRecordDo.getFileName(),
                        ERROR_FILE_PREFIX + batchImportRecordDo.getFileName(),
                        ContentType.APPLICATION_OCTET_STREAM.toString(),
                        new ByteArrayInputStream(byteArrayOutputStream.toByteArray())));
                // oss异常
                if (org.apache.commons.lang.StringUtils.isBlank(errorFileUrl)) {
                    throw new CorpBusinessException(OSS_ERROR);
                }
                batchImportRecordDo.setErrorFileUrl(errorFileUrl);
            } else {
                batchImportRecordDo.setStatus(BatchImportStatusEnum.SUCCESS.name());
            }
            transactionTemplate.execute(status -> {
                batchImportRecordMapper.insertSelective(batchImportRecordDo);
                if (CollectionUtils.isNotEmpty(batchImportErrorLineDoList)) {
                    batchImportErrorLineDoList.forEach(item -> {
                        item.setRecordId(batchImportRecordDo.getId());
                        item.setDeleted(DeletedEnum.FALSE.getCode());
                        item.setCreateTime(new Date());
                        item.setUpdateTime(new Date());
                    });
                    batchImportErrorLineMapper.batchInsert(batchImportErrorLineDoList);
                }
                return null;
            });
            log.info("【批量导入】批量导入成功 batchImportRecordDo={} fillErrorEntityList={}",
                    JsonUtils.toJsonString(batchImportRecordDo), JsonUtils.toJsonString(fillErrorEntityList));
            DownloadResponse response = new DownloadResponse();
            response.setStatus(batchImportRecordDo.getStatus());
            response.setLineCount(lineCount);
            response.setErrorCount(batchImportRecordDo.getSuccessCount());
            response.setSuccessCount(errorCount);
            response.setMessage("共导入"+ lineCount +"条，成功"+ batchImportRecordDo.getSuccessCount()+"个，失败" + errorCount + "个");
            return response;

        } catch (Exception e) {
            log.error("同步兑换码失败", e);
            log.error("同步兑换码失败", e.getMessage());
            throw new CorpBusinessException(OrganizationResponseCodeEnum.SPREADSHEET_FORMAT_ERROR);
        }
    }

    @Override
    public boolean bindRedemptionCode(String uid, String uname) {
        try {
            log.info("绑定兑换码：uid:{},uname:{}", uid, uname);
            Example example = new Example(MbRedemptionCode.class);
            example.createCriteria().andIsNull("uid");
            MbRedemptionCode mbRedemptionCode = mbRedemptionCodeMapper.selectByExample(example).stream().findFirst()
                    .orElse(null);
            if (mbRedemptionCode == null) {
                return false;
            }
            mbRedemptionCode.setUid(uid);
            mbRedemptionCode.setName(uname);
            mbRedemptionCodeMapper.updateByPrimaryKey(mbRedemptionCode);
            log.info("绑定兑换码成功：uid:{},code:{}", uid, mbRedemptionCode.getRedemptionCode());
            return true;
        } catch (Exception e) {
            log.error("绑定兑换码失败", e);
            return false;
        }
    }

    @Override
    public RedemptionCodeVo getRedemptionCode(String uid) {
        Example example = new Example(MbRedemptionCode.class);
        example.createCriteria().andEqualTo("uid", uid);
        return mbRedemptionCodeMapper.selectByExample(example).stream().findFirst().map(this::convertToRedemptionCodeVo)
                .orElse(null);
    }

    private RedemptionCodeVo convertToRedemptionCodeVo(MbRedemptionCode mbRedemptionCode) {
        RedemptionCodeVo redemptionCodeVo = new RedemptionCodeVo();
        redemptionCodeVo.setCode(mbRedemptionCode.getRedemptionCode());
        redemptionCodeVo.setCodeLink(mbRedemptionCode.getRedemptionCodeLink());
        redemptionCodeVo.setUid(mbRedemptionCode.getUid());
        redemptionCodeVo.setName(mbRedemptionCode.getName());
        return redemptionCodeVo;
    }


    @Override
    public PageBatchImportJobRecordRespVo pageBatchImportRedemption(PageBatchImportJobRecordReqVo requestParam) {
        PageBatchImportJobRecordRespVo result = new PageBatchImportJobRecordRespVo();
        List<String> typeList = Collections.singletonList(BatchImportTypeEnum.REDEMPTION.name());
        List<BatchImportRecordDo> batchImportRecordDoList = batchImportRecordMapper.pageBatchImportJobRecordDo(typeList,
          requestParam.getFileName(), requestParam.getOperatorName(), requestParam.getPageNum(), requestParam.getPageSize());
         if (CollectionUtils.isEmpty(batchImportRecordDoList)) {
            result.setRecordCount(0);
            result.setBatchImportJobRecordList(new ArrayList<>(0));
            return result;
        }
        result.setRecordCount(batchImportRecordMapper.countBatchImportJobRecordDo(typeList));
        List<PageBatchImportJobRecordRespVo.BatchImportJobRecord> batchImportJobRecordList = batchImportRecordDoList
                .stream().map(PageBatchImportJobRecordRespVo.BatchImportJobRecord::new).collect(Collectors.toList());
        batchImportJobRecordList.forEach(item -> {
            item.setTypeName(BatchImportTypeEnum.REDEMPTION.getName());
            // 只有导入失败的才能下载
            if (StringUtils.isNotBlank(item.getProcessedFile())) {
                String ossUrl = basicOssClientLoader.privateGeneratePresignedUrl(item.getProcessedFile());
                if (Boolean.TRUE.equals(checkFileUrl(ossUrl))) {
                    item.setProcessedFile(ossUrl);
                }
            }
        });
        result.setBatchImportJobRecordList(batchImportJobRecordList);
        return result;
    }
    private Boolean checkFileUrl(String fileUrl) {
        String regex = "^https:\\/\\/[a-zA-Z0-9\\-\\.]+\\.[a-zA-Z]{2,}(\\/\\S*)?$";
        return ReUtil.isMatch(regex, fileUrl);
    }

}
